In this project, we’ll use the Myduino AIoT Education Kit, an ultrasonic sensor, a servo motor, and an I2C LCD to build an ESP32 Distance-Controlled Servo System. The servo motor will automatically rotate based on the distance detected by the ultrasonic sensor, and you’ll see a real-time visual feedback on the LCD with both angle display and a custom loading bar animation.
Perfect for students, makers, or IoT beginners who want to explore distance sensing, servo control, and LCD graphics with ESP32. Let’s go from zero to hero!
Objectives
In this project, you’ll learn how to:
- Measure distances using an ultrasonic sensor (HC-SR04)
- Control servo motor position based on sensor readings
- Create custom characters for pixel-perfect LCD graphics
- Display real-time servo angle and loading bar animation
- Map sensor values to actuator movements in embedded systems
By the end, you’ll have your own Distance-Controlled Servo System running on Myduino AIoT Education Kit!
Circuit Connections

| Components | ESP32 Dev Module |
| Servo | IO18 |
| Ultrasonic TRIG | IO5 |
| Ultrasonic ECHO | IO4 |
| LCD 16×2 I2C | SDA = SDA, SCL = SCL |
Logic Flow
- The ultrasonic sensor measures distance to the nearest object
- If distance is between 5-30cm, the ESP32 maps it to servo angle (0-180°)
- The servo motor rotates to the calculated position
- The LCD displays the current servo angle and a proportional loading bar
- Custom LCD characters create smooth pixel-level loading bar animation
Code Lab
Step 1: Install Library
Before uploading the code, you need to install these libraries in Arduino IDE:
- ESP32Servo.h by Kevin Harrington (for servo motor control)
- LiquidCrystal_I2C.h by Frank de Brabander (for I2C LCD)
For example:

Step 2: Copy and Paste Code
Copy and paste this code into Arduino IDE:
#include <ESP32Servo.h>
#include <LiquidCrystal_I2C.h>
// Pin definitions
#define SERVO_PIN 18
#define TRIG_PIN 5
#define ECHO_PIN 4
// Components
Servo myServo;
LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C address might be 0x3F if 0x27 doesn't work
// Variables
int servoAngle = 0;
unsigned long previousMillis = 0;
const long interval = 100; // Update interval for ultrasonic
// Custom characters for pixel-level loading bar
byte char0[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // Empty
byte char1[8] = {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}; // 1 pixel
byte char2[8] = {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18}; // 2 pixels
byte char3[8] = {0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C}; // 3 pixels
byte char4[8] = {0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E}; // 4 pixels
byte char5[8] = {0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}; // 5 pixels (full)
void setup() {
Serial.begin(115200);
// Initialize servo
myServo.attach(SERVO_PIN);
myServo.write(0);
// Initialize ultrasonic pins
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// Initialize LCD
lcd.init();
lcd.backlight();
// Create custom characters for loading bar
lcd.createChar(0, char0); // Empty
lcd.createChar(1, char1); // 1 pixel
lcd.createChar(2, char2); // 2 pixels
lcd.createChar(3, char3); // 3 pixels
lcd.createChar(4, char4); // 4 pixels
lcd.createChar(5, char5); // Full block
lcd.setCursor(0, 0);
lcd.print("Angle: ");
Serial.println("System initialized!");
}
long readUltrasonic() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH);
long distance = duration * 0.034 / 2; // Convert to cm
return distance;
}
void updateServoAngle() {
long distance = readUltrasonic();
// Map distance to servo angle (adjust range as needed)
// Assuming 5-30cm distance range maps to 0-180 degrees
if (distance >= 5 && distance <= 30) {
servoAngle = map(distance, 5, 30, 0, 180);
servoAngle = constrain(servoAngle, 0, 180);
myServo.write(servoAngle);
}
// Update LCD display
lcd.setCursor(7, 0);
lcd.print(" "); // Clear previous value
lcd.setCursor(7, 0);
lcd.print(servoAngle);
lcd.write(223); // Degree symbol for LCD
// Update loading bar
updateLoadingBar();
Serial.print("Distance: ");
Serial.print(distance);
Serial.print(" cm, Angle: ");
Serial.println(servoAngle);
}
void updateLoadingBar() {
// Calculate total pixels needed (16 characters × 5 pixels = 80 pixels total)
// Map servo angle (0-180) to pixels (0-80)
int totalPixels = map(servoAngle, 0, 180, 0, 80);
// Clear the second row
lcd.setCursor(0, 1);
for (int i = 0; i < 16; i++) {
int pixelsInThisChar = totalPixels - (i * 5);
if (pixelsInThisChar <= 0) {
lcd.write(0); // Empty character
} else if (pixelsInThisChar >= 5) {
lcd.write(5); // Full character
} else {
lcd.write(pixelsInThisChar); // Partial character (1-4 pixels)
}
}
}
void updateLEDAnimation() {
// LED animation removed
}
void loop() {
unsigned long currentMillis = millis();
// Update servo angle based on ultrasonic sensor
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
updateServoAngle();
}
}
Step 3: Upload and Run
- Connect your ESP32 board
- Upload the code
- Open Serial Monitor at 115200 baud
- Move your hand or objects in front of the ultrasonic sensor (5-30cm range)
System Check
When working properly:
- Moving objects closer/farther changes servo angle smoothly
- LCD displays current servo angle with degree symbol
- Loading bar fills proportionally to servo angle
- Serial Monitor shows distance readings and servo angles
- Servo moves smoothly between 0-180 degrees
Troubleshooting Guide
| Problem | Solution |
| Servo doesn’t move | Check servo power supply and GPIO 18 connection |
| LCD shows nothing | Try I2C address 0x3F instead of 0x27, or check SDA/SCL wiring |
| Erratic distance readings | Ensure ultrasonic sensor is properly connected and powered |
| Loading bar doesn’t display | Verify custom characters are created correctly in setup() |
| Serial Monitor shows no output | Check baud rate is set to 115200 |
Tips: If you want to find any lines in Arduino IDE, click CTRL+F (CMD + F on Mac) and search the line you want to find (applicable to any platform).
How It Works
Distance Measurement
The ultrasonic sensor works by sending out sound waves and measuring the time it takes for them to return. The readUltrasonic( ) function triggers the sensor and calculates distance using the formula: Distance = (Duration × Speed of Sound) / 2
Servo Control
The map( ) function converts the 5-30cm distance range to 0-180° servo angles. The constrain() function ensures the angle stays within valid servo limits.
Custom LCD Graphics
The loading bar uses custom 5×8 pixel characters to create smooth animations. Each LCD character can display 0-5 pixels horizontally, giving us 80 total pixels across 16 characters for precise visualization.






